<?php
//模板类
class View {
    private $_vars=array();//存放模板引擎注入的普通变量
    private $_configs=array();//载入的系统变量
    private $tpl_dir;//模板目录路径
    private $tpl_file;//模板文件路径
    private $parse_dir;//编译目录路径
    private $parse_file;//编译文件路径
    private $cache_dir;//缓存目录路径
    private $cache_file;//缓存文件路径
//模板构造方法,主要完成相关目录是否存在的检测,以及将系统变量的值读入到$_configs变量中
    public function __construct($tpl_dir=''){//如果参数为空，则生成网页模板路径，如果有值，则生成挂件模板路径
        if($tpl_dir==''){
            $this->tpl_dir = APP_PATH.DS.sysC('mod').DS.'Tpl';
            $this->parse_dir = APP_PATH.DS.sysC('mod').DS.'View';
            $this->cache_dir = APP_PATH.DS.sysC('mod').DS.'View';
        }else{
            $this->tpl_dir = APP_PATH.DS.sysC('mod').DS.'Widget'.DS.'Tpl';
            $this->parse_dir = APP_PATH.DS.sysC('mod').DS.'View';
            $this->cache_dir = APP_PATH.DS.sysC('mod').DS.'View';
        }
        $this->is_dir_exists($this->tpl_dir,$this->parse_dir,$this->cache_dir);
        $this->getConfig();
    }
//display()方法：完成与编译,缓存相关的一些功能
    /**
     * @param $file string|array
     */
    public function display($file){//传入参数如果是数组，则渲染网页模板，如果是字符串，则渲染挂件模板
        $file_name= null;
        if(is_array($file)){
            $file_name=$file[2].'.html';
            $this->tpl_file=$this->tpl_dir.DS.$file[1].DS.$file_name;//设置模板文件路径
            if(!file_exists($this->tpl_file)){
                halt($file_name.'模板文件不存在!');
            }
            $this->parse_file=$this->parse_dir.DS.md5($file[1].DS.$file[2]).'.php';//设置编译文件路径
        }else{
            $file_name=$file.'.html';
            $this->tpl_file=$this->tpl_dir.DS.$file_name;//设置挂件模板文件路径
            if(!file_exists($this->tpl_file)){
                halt($file_name.'模板文件不存在!');
            }
            $this->parse_file=$this->parse_dir.DS.md5($file_name).'.php';//设置挂件编译文件路径
        }
        $parse=new Parse($this->tpl_file);//初始化模板解析类
        $parse->compile($this->parse_file,$this->tpl_file);//解析静态模板文件,生成编译文件

//判断是否需要重新生成缓存文件
        $this->cache($file_name);
    }
//assign()方法：接收从index.php文件分配过来的变量的值,将它们保存在$_vars变量中
    public function assign($var,$value){
        if(isset($var)&&!empty($var)){//判断模板变量是否有设置,且不能为空
            $this->_vars[$var]=$value;//接收从index.php文件分配过来的变量的值,将它们保存在$_vars变量中
        }else{
            halt('请设置模板变量!');
        }

    }
//getConfig()方法：将系统变量的值读入到$_configs变量中
    private function getConfig(){
        if(file_exists(APP_PATH.'/config/profile.xml')){//判断系统配置文件是否存在
            $sxe=simplexml_load_file(APP_PATH.'/config/profile.xml');//载入系统配置文件
            $taglib=$sxe->xpath('/root/taglib');//使用xpath()方法读取相关节点
            foreach($taglib as $value){
                $this->_configs["$value->name"]=$value->value;//将系统变量的值读入到$_configs变量中
            }
        }
    }
//is_dir_exists()方法：相关目录是否存在的检测
    private function is_dir_exists($tpl_dir,$parse_dir,$cache_dir){
        if(!is_dir($tpl_dir)){//检测是否存在模板文件夹
            halt('模板文件夹不存在!');
        }
        if(!is_dir($parse_dir)){//检测是否存在编译文件夹
            halt('编译文件夹不存在!');
        }
        if(!is_dir($cache_dir)){//检测是否存在缓存文件夹
            halt('缓存文件夹不存在!');
        }
    }
//cache()方法：完成与缓存相关的一些功能
    private function cache($file){
        $this->cache_file=APP_PATH.DS.sysC('mod').'/View/cache/'.md5($file).'.html';//设置缓存文件路径
//如果开启缓存,缓存文件存在且模板文件没有被修改过,直接载入缓存文件
        if(IS_CACHE){
            if(file_exists($this->cache_file)&&filemtime($this->cache_file)>=filemtime($this->parse_file)){
                include $this->cache_file;//载入缓存文件
                return ;
            }
        }
//判断是否开启缓存,如果开启就生成静态html文件,否则,直接载入编译文件
        IS_CACHE? ob_start():null;
        if(IS_CACHE){
            include $this->parse_file;
            file_put_contents($this->cache_file,ob_get_contents());//生成静态html缓存文件
            ob_end_clean();
            include $this->cache_file;//载入静态html缓存文件
        }else{
            include $this->parse_file;//载入编译文件
        }
    }
}

//模板解析类
class Parse {
    private $_tpl;//存放静态模板文件的内容
//初始化构造方法：读取模板文件内容保存到到$_tpl变量中
    public function __construct($tpl_file){
        if(!$this->_tpl=file_get_contents($tpl_file)){//读取静态模板文件内容到$_tpl变量中
            halt('模板内容读取失败!');
        }
    }
//compile()方法：完成静态模板的解析并生成编译文件
    public function compile($parse_file,$tpl_file){
        $this->parse();//调用私有方法parse()完成各种标签的解析
//如果编译文件不存在或是模板文件被修改过就生成编译文件
        if(!file_exists($parse_file)||filemtime($tpl_file)>=filemtime($parse_file)){
            if(!file_put_contents($parse_file,$this->_tpl)){
                halt('编译文件生成失败!');
            }
        }
    }
//parse()方法：内部调用各种解析方法
    private function parse(){
        $this->parseInclude();
        $this->parseConfig();
        $this->parseCommon();
        $this->parseForeach();
        $this->parseVar();
        $this->parsePrint();
        $this->parseIf();
        $this->parseIsset();
        $this->parseWidget();
    }
//    方法一
//parseInclude()方法：解析普通文件包含标签
    private function parseInclude (){
        $mode='/\{include\s+file=\'(.+)\'\}/';//普通文件包含标签模式
//在模板文件中匹配模式,如果匹配成功,则替换成相应的php语言中的include包含语句
        if(preg_match($mode,$this->_tpl,$file)){
            if(!file_exists(APP_PATH.DS.sysC('mod').'/Tpl/Public/'.$file[1].'.html')){
                halt('包含文件出错!');
            }
            $this->_tpl=preg_replace($mode,"<?php include APP_PATH.DS.sysC('mod').'/Tpl/Public/$1.html';?>",$this->_tpl);//替换成相应的php语言中的include包含语句
        }
    }
//parseVar()方法：解析普通变量
    private function parseVar(){
        $mode='/\{\$([\w]+)\}/';//普通变量模式
//在模板文件中匹配模式,如果匹配成功,则替换成index.php文件中注入的变量
        if(preg_match($mode,$this->_tpl)){
            $this->_tpl=preg_replace($mode,"<?php echo \$this->_vars['$1'];?>",$this->_tpl);//替换成index.php文件中注入的变量
        }
    }
    //parsePrint()方法：解析对象或数组变量
    private function parsePrint(){
        $mode='/\{\[\$([\w]+)\]\}/';//对象或数组变量模式
//在模板文件中匹配模式,如果匹配成功,则替换成index.php文件中注入的变量
        if(preg_match($mode,$this->_tpl)){
            $this->_tpl=preg_replace($mode,"<?php print_r(\$this->_vars['$1']);?>",$this->_tpl);//替换成index.php文件中注入的变量
        }
    }
//parseIf()方法：解析if语句
    private function parseIf(){
        //if语句模式
        $modeIf='/\{if\s+\$([\w]+)\}/';
        $modeEndIf='/\{\/if\}/';
        $modeElse='/\{else\}/';
//在模板文件中匹配模式,如果匹配成功,则替换成相应的php语言中的if语句
        if(preg_match($modeIf,$this->_tpl)){
            if(preg_match($modeEndIf,$this->_tpl)){
                $this->_tpl=preg_replace($modeIf,"<?php if(\$this->_vars['$1']){?>",$this->_tpl);
                $this->_tpl=preg_replace($modeEndIf,"<?php }?>",$this->_tpl);
                if(preg_match($modeElse,$this->_tpl)){
                    $this->_tpl=preg_replace($modeElse,"<?php }else{?>",$this->_tpl);
                }
            }else{
                halt('If语句没有关闭!');
            }
        }
    }

    //parseIf()方法：解析ifisset语句
    private function parseIsset(){
        //if语句模式
        $modeIf='/\{isset\s+\$([\w]+)\}/';
        $modeEndIf='/\{\/isset\}/';
        $modeElse='/\{else\}/';
//在模板文件中匹配模式,如果匹配成功,则替换成相应的php语言中的isset语句
        if(preg_match($modeIf,$this->_tpl)){
            if(preg_match($modeEndIf,$this->_tpl)){
                $this->_tpl=preg_replace($modeIf,"<?php if(isset(\$this->_vars['$1'])){?>",$this->_tpl);
                $this->_tpl=preg_replace($modeEndIf,"<?php }?>",$this->_tpl);
                if(preg_match($modeElse,$this->_tpl)){
                    $this->_tpl=preg_replace($modeElse,"<?php }else{?>",$this->_tpl);
                }
            }else{
                halt('If语句没有关闭!');
            }
        }
    }

//parseCommon()方法：解析注释标签
    private function parseCommon(){
        $mode='/{#\s+(.*)\s+}/';//注释标签模式
//在模板文件中匹配模式,如果匹配成功,则替换成相应的php语言中的注释
        if(preg_match($mode,$this->_tpl)){
            $this->_tpl=preg_replace($mode,"<?php /* '$1' */?>",$this->_tpl);//替换成相应的php语言中的注释
        }
    }
//parseConfig()方法：解析系统变量
    private function parseConfig(){
        $mode='/\{\$config\.([\w]+)\}/';//系统变量匹配模式
//在模板文件中匹配模式,如果匹配成功,则替换成读入的系统变量的值
        if(preg_match($mode,$this->_tpl)){
            $this->_tpl=preg_replace($mode,"<?php echo \$this->_configs['$1'];?>",$this->_tpl);//替换成读入的系统变量的值
        }
    }
    //parseWidget()方法：解析widget
    private function parseWidget(){
        $mode='/\{W\s+\'([\w]+)\',\[(\'([\w]+)\'\s*=>\s*\'([\w\x{4e00}-\x{9fa5}]+)\'(,)*)+\]\}/u';//单引号 关联数组形式
//在模板文件中匹配模式,如果匹配成功,则替换成index.php文件中注入的变量
        if(preg_match($mode,$this->_tpl,$var)){
            $leftfir=strpos($var[0],'[');
            $rightfir=strripos($var[0],']');
            $string=substr($var[0],$leftfir+1,$rightfir-$leftfir-1);//截取数组内容
            $this->_tpl=preg_replace($mode,"<?php W('$1',array($string));?>",$this->_tpl);//替换成index.php文件中注入的变量
        }
    }
//parseForeach()方法：解析foreach语句
    private function parseForeach(){
//foreach语句匹配模式
        $modeForeach='/\{foreach\s+key=([\w]+)\s+item=([\w]+)\s+from=\$([\w]+)\}/';
        $modeEndForeach='/\{\/foreach\}/';
        $modeVar='/\{@([\w]+)\}/';
//在模板文件中匹配模式,如果匹配成功,则替换成相应的php语言中的foreach语句
        if(preg_match($modeForeach,$this->_tpl)){
            if(preg_match($modeEndForeach,$this->_tpl)){
                $this->_tpl=preg_replace($modeForeach,"<?php foreach(\$this->_vars['$3'] as \$$1=>\$$2){?>",$this->_tpl);
                $this->_tpl=preg_replace($modeVar,"<?php echo \$$1;?>",$this->_tpl);
                $this->_tpl=preg_replace($modeEndForeach,"<?php }?>",$this->_tpl);
            }else{
                halt('Foreach语句没有关闭!');
            }
        }
    }
}